package composer.gui;

import java.io.IOException;
import java.util.Vector;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.control.MIDIControl;
import javax.microedition.media.control.ToneControl;

import composer.base.Note;
import composer.base.NotesOffThread;
import composer.base.NotesOnThread;
import composer.base.Track;

public class PianoRollScreen extends GameCanvas implements Screen, CommandListener {

	private static final int KEYS_ON_SCREEN = 14;

	private static final int NOTES_PER_BAR = 8;

	private static final byte OCTAVE = 12;

	private Track track;

	private byte cursorNote;

	private int cursorPosition;

	private Graphics g;

	private int H, W;

	private byte defaultCursorNote;

	private byte firstKeyOnScreen;
	private int firstPositionOnScreen;

	private boolean andou;

	private Command playAllCommand;

	private byte[] deltas = new byte[] {0, -1, 6, -1, 5, 4, -1, 3, -1, 2, -1, 1};

	public PianoRollScreen(Track track) {
		super(false);
		this.track = track;
		this.g = this.getGraphics();
		this.H = this.getHeight();
		this.W = this.getWidth();
		this.playAllCommand = new Command("Play All", Command.OK, 1);
	}

	public void enter() {
		this.defaultCursorNote = ToneControl.C4;
		this.firstKeyOnScreen = (byte) (defaultCursorNote + OCTAVE);
		
		this.firstPositionOnScreen = 0;

		this.cursorNote = defaultCursorNote;
		this.cursorPosition = 0;
		
		this.setCommandListener(this);
		this.addCommand(this.playAllCommand);
	}

	public void update() {
		int keys = this.getKeyStates();
		if (keys != 0) {
			if ((keys & UP_PRESSED) != 0) {
				scrollUp();
				cursorNote = (byte) (cursorNote + 1);
			}
			if ((keys & DOWN_PRESSED) != 0) {
				scrollDown();
				cursorNote = (byte) (cursorNote - 1);
			}
			if ((keys & RIGHT_PRESSED) != 0) {
				cursorPosition = cursorPosition + 1;
				if(cursorPosition > getOffRightLimit()){
					this.firstPositionOnScreen += 1;
				}
			}
			if ((keys & LEFT_PRESSED) != 0) {
				if(cursorPosition > firstPositionOnScreen || firstPositionOnScreen > 0){
					cursorPosition = cursorPosition - 1;
				}
				if(cursorPosition < this.firstPositionOnScreen){
					this.firstPositionOnScreen -= 1;
				}
			}
			if((keys & (1 << KEY_STAR)) != 0){
				track.addNote(new Note(cursorNote, 120, cursorPosition, 1));
			}
			//System.out.println("cursor(note, position) = " + cursorNote	+ ", " + cursorPosition);
			this.andou = true;
		}
	}

	private void scrollUp() {
		if (cursorNote == firstKeyOnScreen) {
			int keyCode = firstKeyOnScreen % 12;
			if(keyCode == 4 || keyCode == 11){
				//it's a B or an E
				firstKeyOnScreen = (byte) (firstKeyOnScreen + 1);
			} else {
				firstKeyOnScreen = (byte) (firstKeyOnScreen + 2);
			}
		}
	}

	private void scrollDown() {
		if (getDeltaNotesFromFirstNote(cursorNote) + 2 >= KEYS_ON_SCREEN) {
			int keyCode = firstKeyOnScreen % 12;
			if(keyCode == 0 || keyCode == 5){
				//it's a C or a F
				this.firstKeyOnScreen = (byte) (this.firstKeyOnScreen - 1);
			} else {
				this.firstKeyOnScreen = (byte) (this.firstKeyOnScreen - 2);
			}
		}
	}

	public void paint() {
		clearScreen();

		int barInfoHeight = H * 10 / 75;
		int keyboardWidth = W / 5;
		int commandsHeight = H / 10;
		int keyboardHeight = H - barInfoHeight - commandsHeight;

		int gridCellWidth = 2 * W / (5 * NOTES_PER_BAR);
		int gridCellHeigth = keyboardHeight / KEYS_ON_SCREEN;

		drawBarInfo(barInfoHeight, keyboardWidth);

		g.setClip(0, barInfoHeight, keyboardWidth + 1, keyboardHeight);
		drawKeyboard(barInfoHeight, keyboardWidth, keyboardHeight,
				gridCellHeigth, commandsHeight);
		g.setClip(0, 0, W, H);

		drawGrid(barInfoHeight, keyboardWidth, commandsHeight, keyboardHeight,
				gridCellWidth, gridCellHeigth);

		g.setClip(keyboardWidth, barInfoHeight, 4 * W / 5, keyboardHeight);
		drawTrack(barInfoHeight, keyboardWidth, gridCellWidth, gridCellHeigth);
		drawCursor(barInfoHeight, keyboardWidth, gridCellWidth, gridCellHeigth);
		g.setClip(0, 0, W, H);

		this.flushGraphics();
	}

	private void drawTrack(int barInfoHeight, int keyboardWidth, int gridCellWidth, int gridCellHeigth) {
		//calculate limits for notes that are to be plotted
		int maxTone = firstKeyOnScreen;
		if(isAnAccent(firstKeyOnScreen + 1)){
			maxTone = firstKeyOnScreen + 1;
		}
		
		int minTone = firstKeyOnScreen - ((KEYS_ON_SCREEN * 12) / 7);
		
		Vector notes = track.getNotes();
		for (int i = 0; i < notes.size(); i++) {
			Note note = (Note) notes.elementAt(i);
		
			if(note.getTone() > maxTone || note.getTone() < minTone 
					|| note.getPosition() > getOffRightLimit()
					|| note.getPosition() + note.getDuration() < this.firstPositionOnScreen){
				continue;
			}
			
			int noteYPosition = this.getNoteYPosition(note.getTone(), barInfoHeight, gridCellHeigth);
			int noteXPosition = gridCellWidth * (note.getPosition() - this.firstPositionOnScreen) + keyboardWidth;
			int noteWidth = gridCellWidth * note.getDuration();
			
			g.fillRect(noteXPosition, noteYPosition, noteWidth, gridCellHeigth);
		}
	}

	private int getOffRightLimit() {
		return this.firstPositionOnScreen + 2 * NOTES_PER_BAR;
	}

	private void clearScreen() {
		g.setColor(0xffffffff);
		g.fillRect(0, 0, W, H);
		g.setColor(0x00000000);
	}

	private void drawCursor(int barInfoHeight, int keyboardWidth,
			int gridCellWidth, int gridCellHeigth) {
		int cursorX = ((cursorPosition - firstPositionOnScreen) * gridCellWidth) + keyboardWidth;
		int cursorY = getNoteYPosition(cursorNote, barInfoHeight, gridCellHeigth);
		
		if(andou){
			System.out.println("cursor cursorY, cursorNote, firstKeyOnScreen = " + cursorY + ", " + cursorNote + ", " + firstKeyOnScreen);
			andou = false;
		}
		
		int cursorBorderWidth = 2;

		g.setStrokeStyle(Graphics.DOTTED);
		g.drawRect(cursorX, cursorY, gridCellWidth + cursorBorderWidth,
				gridCellHeigth + cursorBorderWidth);
		g.setStrokeStyle(Graphics.SOLID);
	}

	private int getNoteYPosition(byte note, int barInfoHeight, int gridCellHeigth) {
		int noteYPosition = -1;
		if(isAnAccent(note)){
			int nextNoteYPosition = getNoteYPosition((byte)(note - 1), barInfoHeight, gridCellHeigth);
			noteYPosition = nextNoteYPosition - (gridCellHeigth / 2);
		} else {
			noteYPosition = getDeltaNotesFromFirstNote(note);
			noteYPosition = noteYPosition * gridCellHeigth + barInfoHeight;
		}
		
		return noteYPosition;
	}

	private int getDeltaNotesFromFirstNote(byte note) {
		int count = 0;
		int tmpKey = this.firstKeyOnScreen;
		while(tmpKey > note){
			if(!isAnAccent(tmpKey)){
				count++;
			}
			tmpKey--;
		}
		return count;
	}

	private void drawGrid(int barInfoHeight, int keyboardWidth,
			int commandsHeight, int keyboardHeight, int gridCellWidth,
			int gridCellHeigth) {
		// draw grid
		int linePosition = keyboardWidth + gridCellWidth;
		while (linePosition < W) {
			g.drawLine(linePosition, barInfoHeight, linePosition,
					(barInfoHeight + keyboardHeight));
			linePosition += gridCellWidth;
		}

		linePosition = barInfoHeight + gridCellHeigth;
		while (linePosition < (H - commandsHeight)) {
			g.drawLine(keyboardWidth, linePosition, W, linePosition);
			linePosition += gridCellHeigth;
		}
	}

	private void drawKeyboard(int barInfoHeight, int keyboardWidth,
			int keyboardHeight, int gridCellHeigth, int commandsHeight) {
		// draw keyboard rect
		g.drawRect(0, barInfoHeight, keyboardWidth, keyboardHeight);

		// draw white keys
		int linePosition = barInfoHeight;
		while (linePosition < (H - commandsHeight)) {
			g.drawRoundRect(0, linePosition, keyboardWidth, gridCellHeigth, 8,
					8);
			linePosition += gridCellHeigth;
		}

		// draw black keys
		int blackKeyHeight = getBlackKeyHeight(gridCellHeigth);
		int realNumberOfKeysOnScreen = (KEYS_ON_SCREEN * 12) / 7;
		int key = firstKeyOnScreen;
		if (!isAnAccent(key)) {
			key = key + 1;
			realNumberOfKeysOnScreen++;
		}
		while (key > firstKeyOnScreen - realNumberOfKeysOnScreen) {
			if (isAnAccent(key)) {
				int keyY = getNoteYPosition((byte) key, barInfoHeight,
						gridCellHeigth);
				keyY = keyY + (blackKeyHeight / 2);
				g.fillRect(0, keyY, keyboardWidth / 2, blackKeyHeight);
				key = key - 2;
				if (!isAnAccent(key)) {
					key = key - 1;
				}
			} else {
				key = key - 1;
			}
		}
	}

	private int getBlackKeyHeight(int gridCellHeigth) {
		int blackKeyHeight = gridCellHeigth / 2;
		if (blackKeyHeight % 2 == 1)
			blackKeyHeight++; // adjusting key height
		return blackKeyHeight;
	}

	private boolean isAnAccent(int key) {
		int keyCode = key % 12;
		return keyCode == 1 || keyCode == 3 || keyCode == 6
				|| keyCode == 8 || keyCode == 10;
	}

	private void drawBarInfo(int barInfoHeight, int keyboardWidth) {
		// draw bar info
		g.drawRect(keyboardWidth, 0, 2 * keyboardWidth, barInfoHeight);
		g.drawRect(3 * keyboardWidth, 0, 2 * keyboardWidth, barInfoHeight);
	}

	public void commandAction(Command command, Displayable displayable) {
		if(command == this.playAllCommand){
			Vector tracks = new Vector();
			tracks.addElement(track);
			
			Player p;
			try {
				p = Manager.createPlayer(Manager.MIDI_DEVICE_LOCATOR);
				p.realize();
				
				MIDIControl midiControl = (MIDIControl) p.getControl("javax.microedition.media.control.MIDIControl");
				
				p.prefetch();
				
				Thread notesOnThread = new NotesOnThread(tracks, midiControl, 180);
				Thread notesOffThread = new NotesOffThread(tracks, midiControl, 180);
				notesOnThread.start();
				notesOffThread.start();
				
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (MediaException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}
